#ifndef FITNESS_ENTERA_CU
#define FITNESS_ENTERA_CU

#include <climits>
#include "fitnessEntera.cuh"

float calcularFitnessEnteraCPU(int* poblacion, int posicion, float* matrizTiemposHost){
	float fitness = std::numeric_limits<float>::min();
	float fitnessParcial[cantComputadoras];
	//inicializo fitness parcial
	for(int i = 0; i < cantComputadoras; i++){
		fitnessParcial[i] = 0;
	}

	for(int i = 0; i < cantTareas; i++){
		fitnessParcial[poblacion[posicion + i]] = fitnessParcial[poblacion[posicion + i]] + matrizTiemposHost[i * cantComputadoras + poblacion[posicion + i]];
	}
	//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
	for(int i = 0; i < cantComputadoras; i++){
        if(fitnessParcial[i] > fitness)
            fitness = fitnessParcial[i];
	}
	return -fitness;
}

__device__ float calcularFitnessEnteraGPU(int* poblacion, int posicion, float* matrizTiemposDevice){
	float fitness = INT_MIN;
	//El tamaño de fitnessParcial es = cantComputadorasDevice, hay que ponerlo a mano porque sino da error de compilación.
	float fitnessParcial[32];
	//inicializo fitness parcial
	for(int i = 0; i < cantComputadorasDevice; i++){
		fitnessParcial[i] = 0;
	}
	for(int i = 0; i < cantTareasDevice; i++){
		fitnessParcial[poblacion[posicion + i]] = fitnessParcial[poblacion[posicion + i]] + matrizTiemposDevice[i * cantComputadorasDevice + poblacion[posicion + i]];
	}
	//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
	for(int i = 0; i < cantComputadorasDevice; i++){
        if(fitnessParcial[i] > fitness)
            fitness = fitnessParcial[i];
	}
	return -fitness;
}


__global__ void calcularFitnessEnteraGPUGen3(int* poblacion, float* vectorFitness, float* fitnessOrdenadoDevice, float* matrizTiemposDevice, float* fitnessParcial){
	//El tamaño de fitnessParcial es = cantComputadorasDevice * cantHilosBloqueDevice, hay que ponerlo a mano porque sino da error de compilación.
	int posicionIndividuo = blockIdx.y * cantGenesEnCromosomaDevice;
	int posicionFitness = blockIdx.y;
	int posicionFitnessEnBloque = cantComputadorasDevice * cantHilosBloqueDevice * blockIdx.y;
	int indice;
	for(int i = threadIdx.y + posicionFitnessEnBloque; i < posicionFitnessEnBloque + cantComputadorasDevice * cantHilosBloqueDevice; i = i + cantHilosBloqueDevice){
		fitnessParcial[i] = 0;
		//cuPrintf("indice en fitnessParcial[%d] = \n", i);
	}
	__syncthreads();
	for(int i = posicionIndividuo + threadIdx.y; i < posicionIndividuo + cantGenesEnCromosomaDevice; i = i + cantHilosBloqueDevice){
		indice = threadIdx.y * cantComputadorasDevice + poblacion[i] + posicionFitnessEnBloque;
		//se agrega la suma ya que si hay mas genes que hilos entonces un hilo va a procesar mas de un gen por lo que debe sumar con el valor anterior
		fitnessParcial[indice] = fitnessParcial[indice] + matrizTiemposDevice[(i - posicionIndividuo) * cantComputadorasDevice + poblacion[i]];
		//cuPrintf("indice en fitnessParcial[%d] = \n", indice);//, fitnessParcial[indice]);
	}
	if (threadIdx.y != 0){
		for(int i = 0; i < cantComputadorasDevice; i++){
			atomicAdd(&(fitnessParcial[i + posicionFitnessEnBloque]), fitnessParcial[threadIdx.y * cantComputadorasDevice + i + posicionFitnessEnBloque]);
		}
	}
	//hay que ver cual es el mayor de esos y a ese asignarlo al fitness
	__syncthreads();
	if(threadIdx.y == 0){
		vectorFitness[posicionFitness] = 0;
		float fitness = INT_MIN;
		//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
		for(int i = 0; i < cantComputadorasDevice; i++){
			if(fitnessParcial[i + posicionFitnessEnBloque] > fitness)
				fitness = fitnessParcial[i + posicionFitnessEnBloque];
		}

		vectorFitness[posicionFitness] = -fitness;
		fitnessOrdenadoDevice[posicionFitness] = vectorFitness[posicionFitness];
	}
}

__global__ void calcularFitnessEnteraGPUGen1(int* poblacion, float* vectorFitness, float* fitnessOrdenadoDevice, float* matrizTiemposDevice, float* fitnessParcial){
	float fitnessParcialAux[32];
	//ver si el fitnessParcial no tiene que ser global por el tamaño que es la cantidad de maquinas
	int posicionIndividuo = blockIdx.y * cantGenesEnCromosomaDevice;
	int posicionFitness = blockIdx.y;
	int posBloque = blockIdx.y * cantComputadorasDevice;
	int maquina;

	//entre todos los hilos inicializan el arreglo con los tiempos de cada maquina
	for(int i = threadIdx.y + posBloque; i < cantComputadorasDevice + posBloque; i = i + cantHilosBloqueDevice){
		fitnessParcial[i] = 0;
	}
	//cada hilo inicializa el arreglo auxiliar para hacer el atomic add
	for(int i = threadIdx.y; i < cantComputadorasDevice; i = i++){
		fitnessParcialAux[i] = 0;
	}
	//cada gen suma el tiempo asociado en la posicion de la maquina en la que la tarea se ejecuta del arreglo auxiliar
	for(int i = posicionIndividuo + threadIdx.y; i < posicionIndividuo + cantGenesEnCromosomaDevice; i = i + cantHilosBloqueDevice){
		maquina = poblacion[i];
		fitnessParcialAux[maquina] = fitnessParcialAux[maquina] + matrizTiemposDevice[(i - posicionIndividuo) * cantComputadorasDevice + maquina];
	}
	//se realiza un atomicAdd por cada maquina para saber cual es el tiempo que tardo cada maquina en ejecutar todas las tareas
	for(int i = 0; i < cantComputadorasDevice; i++){
		atomicAdd(&(fitnessParcial[i + posBloque]), fitnessParcialAux[i]);
	}
	//hay que ver cual es el mayor de esos y a ese asignarlo al fitness
	__syncthreads();
	if(threadIdx.y == 0){
		vectorFitness[posicionFitness] = 0;
		float fitness = INT_MIN;
		//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
		for(int i = 0; i < cantComputadorasDevice; i++){
			if(fitnessParcial[i + posBloque] > fitness)
				fitness = fitnessParcial[i + posBloque];
		}
		vectorFitness[posicionFitness] = -fitness;
		fitnessOrdenadoDevice[posicionFitness] = vectorFitness[posicionFitness];
	}
}


__global__ void calcularFitnessEnteraGPUGen4(int* poblacion, float* vectorFitness, float* fitnessOrdenadoDevice, float* matrizTiemposDevice, float* fitnessParcial){
	//ver si el fitnessParcial no tiene que ser global por el tamaño que es la cantidad de maquinas
	int posicionIndividuo = blockIdx.y * cantGenesEnCromosomaDevice;
	int posicionFitness = blockIdx.y;
	int posBloque = blockIdx.y * cantComputadorasDevice;
	float valorParcial = 0;
	int indiceMatriz, indiceFitness, maquina;

	for(int i = threadIdx.y + posBloque; i < cantComputadorasDevice + posBloque; i = i + cantHilosBloqueDevice){
		fitnessParcial[i] = 0;
	}
	__syncthreads();
	//cada gen suma el tiempo asociado en la posicion de la maquina en posicion correspondiente a la máquina del arreglo de fitnessParcial
	for(int i = posicionIndividuo + threadIdx.y; i < posicionIndividuo + cantGenesEnCromosomaDevice; i = i + cantHilosBloqueDevice){
		maquina = poblacion[i];
		indiceMatriz = (i - posicionIndividuo) * cantComputadorasDevice + maquina;
		if (indiceMatriz < cantComputadorasDevice * cantGenesEnCromosomaDevice) {
			valorParcial = matrizTiemposDevice[indiceMatriz];
			indiceFitness = posBloque + maquina;
			atomicAdd(&(fitnessParcial[indiceFitness]), valorParcial);
			//cuPrintf("valorParcial = %f - ", valorParcial);
		}else{
			cuPrintf("FUERA DE RANGO!!! indiceMatriz = %d, i = %d, posicionIndividuo = %d, cantComputadorasDevice = %d, maquina = %d \n", indiceMatriz, i, posicionIndividuo, cantComputadorasDevice, maquina);
		}
	}
	//cuPrintf("valorParcial = %f	\n", valorParcial);
	//hay que ver cual es el mayor de esos y a ese asignarlo al fitness
	__syncthreads();
	if(threadIdx.y == 0){
		vectorFitness[posicionFitness] = 0;
		float fitness = INT_MIN;
		//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
		for(int i = 0; i < cantComputadorasDevice; i++){
			if(fitnessParcial[i + posBloque] > fitness)
				fitness = fitnessParcial[i + posBloque];
		}
		vectorFitness[posicionFitness] = -fitness;
		fitnessOrdenadoDevice[posicionFitness] = vectorFitness[posicionFitness];
	}
}

__global__ void calcularFitnessEnteraGPUGen(int* poblacion, float* vectorFitness, float* fitnessOrdenadoDevice, float* matrizTiemposDevice, int cantHilosFitnessDevice){
	//El tamaño de fitnessParcial es = cantComputadorasDevice * cantHilosFitnessDevice se pasa como parametro de la funcion calcularFitnessEnteraGPUGen y por eso aca adentro se declara como extern.
	extern __shared__ float fitnessParcial[];
	int posicionIndividuo = blockIdx.y * cantGenesEnCromosomaDevice;
	int posicionFitness = blockIdx.y;
	int indice;
	int maquina;

	for(int i = threadIdx.y; i < cantComputadorasDevice * cantHilosFitnessDevice; i = i + cantHilosFitnessDevice){
		fitnessParcial[i] = 0;
	}
	__syncthreads();
	//hay que inicializar fitnessParcial y volverlo a 0 cada vez antes de llamar al calcular fitness
	for(int i = posicionIndividuo + threadIdx.y; i < posicionIndividuo + cantGenesEnCromosomaDevice; i = i + cantHilosFitnessDevice){
		maquina = poblacion[i];
		indice = threadIdx.y * cantComputadorasDevice + maquina;
		fitnessParcial[indice] = fitnessParcial[indice] + matrizTiemposDevice[(i - posicionIndividuo) * cantComputadorasDevice + maquina];
	}
	/*if (threadIdx.y != 0){
		for(int i = 0; i < cantComputadorasDevice; i++){
			atomicAdd(&(fitnessParcial[i]), fitnessParcial[threadIdx.y * cantComputadorasDevice + i]);
		}
	}*/

	int j = cantComputadorasDevice * cantHilosFitnessDevice / 2;
	while (j != cantComputadorasDevice/2) {
		for(int i = threadIdx.y; i < j; i = i + cantHilosFitnessDevice){
		//if (threadIdx.y < j) {
			fitnessParcial[i] = fitnessParcial[i] + fitnessParcial[i + j];
		}
		__syncthreads();
		j = j / 2;
	}

	//hay que ver cual es el mayor de esos y a ese asignarlo al fitness
	__syncthreads();
	if(threadIdx.y == 0){
		vectorFitness[posicionFitness] = 0;
		float fitness = INT_MIN;
		//Me quedo con la computadora que mas tardo en procesar las tareas asignadas
		for(int i = 0; i < cantComputadorasDevice; i++){
			//cuPrintf("fitnessParcial[%d] = %f\n",i,fitnessParcial[i]);
			if(fitnessParcial[i] > fitness)
				fitness = fitnessParcial[i];
		}
		vectorFitness[posicionFitness] = -fitness;
		fitnessOrdenadoDevice[posicionFitness] = vectorFitness[posicionFitness];
	}
}

// Parsea una matriz a partir de un archivo de texto de nombre "matriz.txt" el cual debe encontrarse en el directorio actual
// /ag-cuda-framework/fitness/ El formato del archivo debe ser el siguiente: Una unica columna de datos donde cada fila es almacenada
// verticalmente
int parserMatriz(float* matriz, int filas, int columnas){
	char str[15];
	float value;
	FILE *fp = fopen("matrizEntera.txt", "r");
	if(fp == NULL){
		return -1;
	}else{
//	printf("***************\n");
		for(int i = 0; i < filas * columnas; i++){
			value = 0;
			fscanf(fp, "%[^\n]", str);
			value = atof(str);
			getc(fp); //leer fin de linea
			matriz[i] = value;
			//printf("%f ", matriz[i]);
			//if ((i + 1) % columnas == 0)
			//	printf("\n");
		}
	}
	//printf("\n************\n");
	fclose(fp);
	return 0;
}

#endif

